#include <stdio.h>
#include <stdint.h>
#include <sys/mman.h>
#include "FLOAT.h"

extern char _vfprintf_internal;
extern char _fpmaxtostr;
extern char _ppfs_setargs;
extern int __stdio_fwrite(char *buf, int len, FILE *stream);

__attribute__((used)) static int format_FLOAT(FILE *stream, FLOAT f) {
	/* TODO: Format a FLOAT argument `f' and write the formating
	 * result to `stream'. Keep the precision of the formating
	 * result with 6 by truncating. For example:
	 *              f          result
	 *         0x00010000    "1.000000"
	 *         0x00013333    "1.199996"
	 */
	char buf[80];

	int sign = (unsigned int) f >> 31;
	FLOAT f_abs = Fabs(f);
	buf[0] = '-';

	int len = sprintf(buf + sign, "%d", f_abs >> 16) + sign;

	buf[len++] = '.';
	int frac = f_abs & 0xffff;
	int i;
	for (i = 0; i < 6; i++) {
		frac *= 10;
		buf[len++] = '0' + frac / 0x10000;
		while (frac >= 0x10000) {
			frac -= 0x10000;
		}
	}
	buf[len] = '\0';

	return __stdio_fwrite(buf, len, stream);
}

static void modify_vfprintf() {
	/* TODO: Implement this function to hijack the formating of "%f"
	 * argument during the execution of `_vfprintf_internal'. Below
	 * is the code section in _vfprintf_internal() relative to the
	 * hijack.
	 */
	
	unsigned int _vfprintf_internal__call_offset = 0x306;
	unsigned int _vfprintf_internal_addr = (unsigned int)(&_vfprintf_internal);
	unsigned int _vfprintf_internal_call_addr = _vfprintf_internal_addr + _vfprintf_internal__call_offset;
	unsigned int _vfprintf_internal_call_addr_addr = _vfprintf_internal_call_addr + 1;
	unsigned int func_offset = (unsigned int)(&_fpmaxtostr) - (unsigned int)(&format_FLOAT);

	// mprotect((void *)(_vfprintf_internal_call_addr & 0xfffff000), 4096 * 2, PROT_READ | PROT_WRITE | PROT_EXEC);

	*((int *)_vfprintf_internal_call_addr_addr) -= func_offset;
	

	unsigned int fstp_addr = _vfprintf_internal_call_addr - 10;
	unsigned int fld1_addr = _vfprintf_internal_call_addr - 0x1E;
	unsigned int fld2_addr = _vfprintf_internal_call_addr - 0x22;
	unsigned int add_esp_addr = _vfprintf_internal_call_addr + 7;

	// deal with the esp
	*((char *)fstp_addr - 1) = 0x08;
	*((char *)add_esp_addr) = 0x20;

	// push the right arg
	*((char *)fstp_addr) = 0xFF;
	*((char *)(fstp_addr + 1)) = 0x32;
	*((char *)(fstp_addr + 2)) = 0x90;

		// nop the fld
	*((char *)fld1_addr) = 0x90;
	*((char *)fld1_addr + 1) = 0x90;
	*((char *)fld2_addr) = 0x90;
	*((char *)fld2_addr + 1) = 0x90;

#if 0
	else if (ppfs->conv_num <= CONV_A) {  /* floating point */
		ssize_t nf;
		nf = _fpmaxtostr(stream,
				(__fpmax_t)
				(PRINT_INFO_FLAG_VAL(&(ppfs->info),is_long_double)
				 ? *(long double *) *argptr
				 : (long double) (* (double *) *argptr)),
				&ppfs->info, FP_OUT );
		if (nf < 0) {
			return -1;
		}
		*count += nf;

		return 0;
	} else if (ppfs->conv_num <= CONV_S) {  /* wide char or string */
#endif

	/* You should modify the run-time binary to let the code above
	 * call `format_FLOAT' defined in this source file, instead of
	 * `_fpmaxtostr'. When this function returns, the action of the
	 * code above should do the following:
	 */

#if 0
	else if (ppfs->conv_num <= CONV_A) {  /* floating point */
		ssize_t nf;
		nf = format_FLOAT(stream, *(FLOAT *) *argptr);
		if (nf < 0) {
			return -1;
		}
		*count += nf;

		return 0;
	} else if (ppfs->conv_num <= CONV_S) {  /* wide char or string */
#endif

}

static void modify_ppfs_setargs() {
	/* TODO: Implement this function to modify the action of preparing
	 * "%f" arguments for _vfprintf_internal() in _ppfs_setargs().
	 * Below is the code section in _vfprintf_internal() relative to
	 * the modification.
	 */

	 unsigned int _ppfs_setargs_addr = (unsigned int) (&_ppfs_setargs);

	 unsigned int _ppfs_setargs_to_jmp_addr = _ppfs_setargs_addr + 0x71;

	 *(char *)_ppfs_setargs_to_jmp_addr = 0xEB;
	 *(char *)(_ppfs_setargs_to_jmp_addr + 1) = 0x30;
	 *(char *)(_ppfs_setargs_to_jmp_addr + 2) = 0x90;

#if 0
	enum {                          /* C type: */
		PA_INT,                       /* int */
		PA_CHAR,                      /* int, cast to char */
		PA_WCHAR,                     /* wide char */
		PA_STRING,                    /* const char *, a '\0'-terminated string */
		PA_WSTRING,                   /* const wchar_t *, wide character string */
		PA_POINTER,                   /* void * */
		PA_FLOAT,                     /* float */
		PA_DOUBLE,                    /* double */
		__PA_NOARG,                   /* non-glibc -- signals non-arg width or prec */
		PA_LAST
	};

	/* Flag bits that can be set in a type returned by `parse_printf_format'.  */
	/* WARNING -- These differ in value from what glibc uses. */
#define PA_FLAG_MASK		(0xff00)
#define __PA_FLAG_CHAR		(0x0100) /* non-gnu -- to deal with hh */
#define PA_FLAG_SHORT		(0x0200)
#define PA_FLAG_LONG		(0x0400)
#define PA_FLAG_LONG_LONG	(0x0800)
#define PA_FLAG_LONG_DOUBLE	PA_FLAG_LONG_LONG
#define PA_FLAG_PTR		(0x1000) /* TODO -- make dynamic??? */

	while (i < ppfs->num_data_args) {
		switch(ppfs->argtype[i++]) {
			case (PA_INT|PA_FLAG_LONG_LONG):
				GET_VA_ARG(p,ull,unsigned long long,ppfs->arg);
				break;
			case (PA_INT|PA_FLAG_LONG):
				GET_VA_ARG(p,ul,unsigned long,ppfs->arg);
				break;
			case PA_CHAR:	/* TODO - be careful */
				/* ... users could use above and really want below!! */
			case (PA_INT|__PA_FLAG_CHAR):/* TODO -- translate this!!! */
			case (PA_INT|PA_FLAG_SHORT):
			case PA_INT:
				GET_VA_ARG(p,u,unsigned int,ppfs->arg);
				break;
			case PA_WCHAR:	/* TODO -- assume int? */
				/* we're assuming wchar_t is at least an int */
				GET_VA_ARG(p,wc,wchar_t,ppfs->arg);
				break;
				/* PA_FLOAT */
			case PA_DOUBLE:
				GET_VA_ARG(p,d,double,ppfs->arg);
				break;
			case (PA_DOUBLE|PA_FLAG_LONG_DOUBLE):
				GET_VA_ARG(p,ld,long double,ppfs->arg);
				break;
			default:
				/* TODO -- really need to ensure this can't happen */
				assert(ppfs->argtype[i-1] & PA_FLAG_PTR);
			case PA_POINTER:
			case PA_STRING:
			case PA_WSTRING:
				GET_VA_ARG(p,p,void *,ppfs->arg);
				break;
			case __PA_NOARG:
				continue;
		}
		++p;
	}
#endif

	/* You should modify the run-time binary to let the `PA_DOUBLE'
	 * branch execute the code in the `(PA_INT|PA_FLAG_LONG_LONG)'
	 * branch. Comparing to the original `PA_DOUBLE' branch, the
	 * target branch will also prepare a 64-bit argument, without
	 * introducing floating point instructions. When this function
	 * returns, the action of the code above should do the following:
	 */

#if 0
	while (i < ppfs->num_data_args) {
		switch(ppfs->argtype[i++]) {
			case (PA_INT|PA_FLAG_LONG_LONG):
			here:
				GET_VA_ARG(p,ull,unsigned long long,ppfs->arg);
				break;
			// ......
				/* PA_FLOAT */
			case PA_DOUBLE:
				goto here;
				GET_VA_ARG(p,d,double,ppfs->arg);
				break;
			// ......
		}
		++p;
	}
#endif

}

void init_FLOAT_vfprintf() {
	modify_vfprintf();
	modify_ppfs_setargs();
}
